﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint.Administration;

namespace Trentacular.SharePoint.StsAdmin
{
    public class SmartSolutionUpgradeCommand : BaseStsadmCommand
    {
        private Parameter FileNameParameter;
        private Parameter FileNameListParameter;

        protected override void CreateParameters()
        {
            FileNameParameter = new OptionalParameter
            {
                Name = "filename",
                Description = "Solution filename"
            };
            Parameters.Add(FileNameParameter);

            FileNameListParameter = new OptionalParameter
            {
                Name = "filenamelist",
                Description = "Path to text file containing each of the solution filenames on separate lines"
            };
            Parameters.Add(FileNameListParameter);
        }

        protected override void ValidateParameters()
        {
            if (FileNameParameter.IsSpecified && FileNameListParameter.IsSpecified)
                throw new InvalidParameterCombinationException(FileNameParameter, FileNameListParameter);

            if (!FileNameParameter.HasValue && !FileNameListParameter.HasValue)
                throw new MissingParameterException(FileNameParameter, FileNameListParameter);
        }

        protected override void Execute()
        {
            try
            {
                if (FileNameParameter.IsSpecified)
                    ProcessSingle(FileNameParameter.Value);
                else
                    ProcessList(FileNameListParameter.Value);
            }
            catch (Exception e)
            {
                ErrorCount++;
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
                throw e;
            }
        }

        private void ProcessList(string listFilePath)
        {
            if (!File.Exists(listFilePath))
                throw new ArgumentException("Filename list file path not found: " + listFilePath);

            ProcessSolutions(File.ReadAllLines(listFilePath));
        }

        private void ProcessSingle(string solutionPath)
        {
            ProcessSolutions(solutionPath);
        }

        private void ProcessSolutions(params string[] solutionPaths)
        {
            foreach (var path in solutionPaths)
            {
                if (!File.Exists(path.Trim()))
                    throw new ArgumentException("Solution path not found: " + path);
            }

            var solutionFilenames = solutionPaths.Select(p => Path.GetFileName(p.Trim()));

            // Take inventory of solution deployments and feature activations
            var inventory = TakeInventory(solutionFilenames);

            // First we will undeploy
            Undeploy(inventory);

            // Now lets perform the deployment
            Redeploy(solutionPaths, inventory);
        }

        private DeploymentInventory TakeInventory(IEnumerable<string> solutionFilenames)
        {
            var gatherer = new SolutionInventoryGatherer(solutionFilenames);
            var inventory = gatherer.GetInventory();
            ErrorCount += gatherer.ErrorCount;

            return inventory;
        }

        private void Undeploy(DeploymentInventory inventory)
        {
            var retractDomain = AppDomain.CreateDomain(
                "Smart Solution Upgrade Retraction App Domain",
                null,
                AppDomain.CurrentDomain.SetupInformation);

            try
            {
                var retractWorker = (RetractWorker)retractDomain.CreateInstanceAndUnwrap(
                    Assembly.GetExecutingAssembly().FullName,
                    typeof(RetractWorker).FullName
                    );

                ErrorCount += retractWorker.DoWork(inventory);
            }
            finally
            {
                AppDomain.Unload(retractDomain);
            }
        }

        private void Redeploy(string[] solutionPaths, DeploymentInventory inventory)
        {
            var deployAppDomain = AppDomain.CreateDomain(
                "Smart Solution Upgrade Deployment App Domain",
                null,
                AppDomain.CurrentDomain.SetupInformation);

            try
            {
                var deployWorker = (DeployWorker)deployAppDomain.CreateInstanceAndUnwrap(
                    Assembly.GetExecutingAssembly().FullName,
                    typeof(DeployWorker).FullName
                    );

                ErrorCount += deployWorker.DoWork(solutionPaths, inventory);
            }
            finally
            {
                AppDomain.Unload(deployAppDomain);
            }
        }
    }

    public class RetractWorker : BaseWorker
    {
        public int DoWork(DeploymentInventory inventory)
        {
            var report = JobDefinitionExecuter.ExecuteJobDefinitions();

            // Lets get out of here if there are already failed jobs
            if (report.HasFailedJobs)
                throw new ApplicationException("Smart Solution Upgrade cannot continue when there are existing failed job definitions");

            var deploymentManager = new DeploymentManager
            {
                JobDefinitionExecuter = JobDefinitionExecuter
            };

            DeactivateSolutionFeatures(inventory);

            var farm = SPFarm.Local;

            // Retract and delete each solution in the inventory
            foreach (var deployInfo in inventory.Values)
            {
                var solution = farm.Solutions[deployInfo.SolutionId];
                deploymentManager.RetractAllSolutionDeployments(solution);
                deploymentManager.DeleteSolution(solution);
            }

            ErrorCount += deploymentManager.ErrorCount;
            ErrorCount += JobDefinitionExecuter.ErrorCount;
            return ErrorCount;
        }

        private void DeactivateSolutionFeatures(DeploymentInventory inventory)
        {
            var featureActivator = GetFeatureActivator(inventory);
            featureActivator.DeactivateAllInventoriedFeatures();
            ErrorCount += featureActivator.ErrorCount;
        }
    }

    public class DeployWorker : BaseWorker
    {
        public int DoWork(string[] solutionPaths, DeploymentInventory inventory)
        {
            var deploymentManager = new DeploymentManager
            {
                JobDefinitionExecuter = JobDefinitionExecuter
            };

            // Now add and redeploy the new solutions
            foreach (var solutionPath in solutionPaths)
            {
                var solution = deploymentManager.AddSolution(solutionPath);

                // Skip deployments if adding the solution failed
                if (solution == null)
                    continue;

                var solutionFilename = Path.GetFileName(solutionPath).ToLower();

                // Skip deployments if couldn't find any inventory information
                if (!inventory.ContainsKey(solutionFilename))
                    continue;

                var deployInfo = inventory[solutionFilename];

                // Skip deployments if solution was not previously deployed
                if (!deployInfo.Deployed)
                    continue;

                deploymentManager.DeploySolution(solution, deployInfo.DeployedWebApplications);
            }

            RestoreSolutionFeatureActivations(inventory);

            ErrorCount += deploymentManager.ErrorCount;
            ErrorCount += JobDefinitionExecuter.ErrorCount;
            return ErrorCount;
        }

        private void RestoreSolutionFeatureActivations(DeploymentInventory inventory)
        {
            // Now reactivate features that were previously activated
            var featureActivator = GetFeatureActivator(inventory);
            featureActivator.RestoreInventoriedFeatureActivations();
            ErrorCount += featureActivator.ErrorCount;
        }
    }

    public class BaseWorker : MarshalByRefObject
    {
        protected JobDefinitionExecutor JobDefinitionExecuter = new JobDefinitionExecutor();
        protected int ErrorCount = 0;

        protected FeatureActivator GetFeatureActivator(DeploymentInventory inventory)
        {
            return new FeatureActivator(inventory)
            {
                JobDefinitionExecuter = JobDefinitionExecuter
            };
        }
    }
}
